<?php

namespace core\lib;

/**
 * redis缓存类
 */
class Redis
{
    // redis对象
    private static $rd;
    // 连接超时时间，秒
    protected $timeout = 300;
    // 主机IP
    public $ip = '';
    // 端口号
    public $port = 6379;
    // 默认db号
    public $dbindex = 0;
    // 连接密码
    private $passward = '';

    public function __construct()
    {
        $this->ip = env('redis.hostname', '127.0.0.1');
        $this->passward = env('redis.auth', '');
        $this->dbindex = !empty(env('redis.databaseIndex', '2')) ? intval(env('redis.databaseIndex', '2')) : 0;
        $this->port = !empty(env('redis.port', 6379)) ? intval(env('redis.port', 6379)) : 6379;
        if (is_null(self::$rd)) {
            self::$rd = new \Redis();
            self::$rd->connect($this->ip, $this->port, $this->timeout);
            if (!empty($this->passward)) {
                self::$rd->auth($this->passward);
            }
        }
        if (!self::$rd->select($this->dbindex)) {
            error('Redis Failed to select database', 500);
        }
    }

    /**
     * 开始事务
     */
    public function multi()
    {
        self::$rd->multi();
    }

    /**
     * 结束事务并提交
     */
    public function exec()
    {
        self::$rd->exec();
    }

    public function set($key, $val)
    {
        if (is_array($val)) {
            return self::$rd->set($key, json_encode($val, JSON_UNESCAPED_UNICODE));
        }
        return self::$rd->set($key, $val);
    }

    public function get($key)
    {
        if (self::$rd->exists($key)) {
            $res = self::$rd->get($key);
            if (isJson($res)) {
                return json_decode($res, true);
            }
            return $res;
        }
        return false;
    }

    /**
     * 重命名
     */
    public function rename(string $oldkey, string $newKey)
    {
        if (self::$rd->exists($oldkey)) {
            return self::$rd->rename($oldkey, $newKey);
        }
        return false;
    }

    public function decr($key, $num = 1)
    {
        if (self::$rd->exists($key)) {
            return self::$rd->decr($key, $num);
        }
        return false;
    }

    public function incr($key, $num = 1)
    {
        if (self::$rd->exists($key)) {
            return self::$rd->incr($key, $num);
        }
        return false;
    }

    public function exists($key)
    {
        if (self::$rd->exists($key)) {
            return true;
        }
        return false;
    }

    public function del($key)
    {
        if (self::$rd->exists($key)) {
            return self::$rd->del($key);
        }
        return true;
    }

    public function expire($key, $timeout)
    {
        if (self::$rd->exists($key)) {
            return self::$rd->expire($key, $timeout);
        }
        return false;
    }

    /**
     * 移动指定库中key移到目标库
     */
    public function move($key, $target_db)
    {
        if (self::$rd->exists($key)) {
            return self::$rd->move($key, $target_db);
        }
        return false;
    }

    /**
     * 清空所有 慎用
     */
    public function flushAll()
    {
        return self::$rd->flushAll();
    }

    /**
     * 清空该DB
     */
    public function flushDB()
    {
        return self::$rd->flushDB();
    }

    public function info()
    {
        return self::$rd->info();
    }

    //===========   redis list 链表队列类型操作方法（适合做评论,分页,消息队列）   ==========

    /**
     * 右边新增
     */
    public function rpush($key, $val)
    {
        if (is_array($val)) {
            foreach ($val as $vo) {
                $res = self::$rd->rPush($key, $vo);
            }
        } else {
            $res = self::$rd->rPush($key, $val);
        }
        //返回添加后元素个数,即list的长度
        return $res;
    }

    /**
     * 左边新增
     */
    public function lpush($key, $val)
    {
        if (is_array($val)) {
            foreach ($val as $vo) {
                $res = self::$rd->lPush($key, $vo);
            }
        } else {
            $res = self::$rd->lPush($key, $val);
        }
        //返回添加后元素个数,即list的长度
        return $res;
    }

    /**
     * 左边删除
     */
    public function lpop($key)
    {
        // 返回被删除的元素
        return self::$rd->lPop($key);
    }

    /**
     * 右边删除
     */
    public function rpop($key)
    {
        return self::$rd->rPop($key);
    }

    /**
     * 保留区域类的元素，其他的删除
     * $redis->ltrim('list1',0,0); 清空list内所有元素
     */
    public function ltrim($key, $start = 0, $end = -1)
    {
        return self::$rd->lTrim($key, $start, $end);
    }

    /**
     * 移除等于value的元素，当count>0时，从表头开始查找，移除count个；当count=0时，从表头开始查找，移除所有等于value的；当count<0时，从表尾开始查找，移除count个
     * $redis->lrem('list1','c','-1'); //移除最后一个c
     */
    public function lrem($key, $value, $count = 0)
    {
        return self::$rd->lRem($key, $value, $count);
    }

    /**
     * 设置列表指定索引的值，如果指定索引不存在则报错
     */
    public function lset($key, $index, $value)
    {
        return self::$rd->lSet($key, $index, $value);
    }

    /**
     * 查询列表长度
     */
    public function llen($key)
    {
        return self::$rd->lLen($key);
    }

    public function lrange($key, $start = 0, $end = -1)
    {
        return self::$rd->lRange($key, $start, $end);
    }

    /**
     * 通过索引index获取列表的元素、key>=0从头到尾，key<0从尾到头
     */
    public function lindex($key, $index)
    {
        return self::$rd->lindex($key, $index);
    }

    //===========   redis hash 哈希类型操作方法 （适合存储对象，如用户信息）  =========

    /**
     * 设置hash值,新增成功返回1，修改成功返回0
     */
    public function hset($name, $key, $value)
    {
        if (is_array($value)) {
            return self::$rd->hset($name, $key, json_encode($value, JSON_UNESCAPED_UNICODE));
        }
        return self::$rd->hset($name, $key, $value);
    }

    /**
     * 批量为多个字段设置hash值,参数$array必为数组
     */
    public function hmset($name, $array)
    {
        return self::$rd->hMset($name, $array);
    }

    /**
     * 读取hash值
     */
    public function hget($name, $key = null)
    {
        if ($key) {
            $res = self::$rd->hget($name, $key);
            if ($this->isJson($res)) {
                $res = json_decode($res, TRUE);
                return $this->json2array($res);
            }
            return $res;
        }
        $res = self::$rd->hgetAll($name);
        if ($res) {
            return $this->json2array($res);
        }
        return false;
    }

    /**
     * 删除hash值
     */
    public function hdel($name, $key = '')
    {
        if ($key) {
            return self::$rd->hDel($name, $key);
        } else {
            $ar = self::$rd->hKeys($name);
            if ($ar) {
                foreach ($ar as $one) {
                    self::$rd->hDel($name, $one);
                }
            }
            return true;
        }
    }

    /**
     * 读取相同前缀的key(返回所有相同前缀的key数组)
     */
    public function scan($same = '')
    {
        $iterator = null;
        $ar = [];
        while (true) {
            $keys = self::$rd->scan($iterator, $same . '*');
            if ($keys === false) {//迭代结束，未找到匹配pattern的key
                return $ar;
            }
            foreach ($keys as $key) {
                $ar[] = $key;
            }
        }
        return $ar;
    }

    /**
     * 是否json字符串
     */
    public function isJson($string)
    {
        if (is_array($string)) {
            return false;
            exit();
        }
        try {
            $jObject = json_decode($string);
        } catch (Exception $e) {
            return FALSE;
        }
        return (is_object($jObject)) ? TRUE : FALSE;
    }

    /**
     * 把一个多维数组的JSON都转为数组
     */
    public function json2array($ar)
    {
        if (is_array($ar)) {
            foreach ($ar as $key => $vo) {
                if ($this->isJson($vo)) {
                    $new_ar[$key] = $this->json2array(json_decode($vo, TRUE));
                } else {
                    $new_ar[$key] = $vo;
                }
            }
            return $new_ar;
        } else {
            if ($this->isJson($ar)) {
                return json_decode($ar, TRUE);
            } else {
                return $ar;
            }
        }
    }

    //===========   redis set string类型 无序集合类型操作方法（适用做好友标签）   ===========
    //因为set堆放是一堆不重复值的集合，所以可以做全局去重的功能。每个用户标签存一个集合。

    /**
     * 添加集合 返回1 新增 0 更新
     */
    public function sadd($key, $value)
    {
        if (!is_array($value)) {
            $arr = array($value);
        } else {
            $arr = $value;
        }
        foreach ($arr as $row) {
            $res = self::$rd->sAdd($key, $row);
        }
        return $res;
    }

    /**
     * 从集合中删除一个元素
     */
    public function srem($key, $value)
    {
        if (!is_array($value)) {
            $arr = array($value);
        } else {
            $arr = $value;
        }
        foreach ($arr as $row) {
            $res = self::$rd->sRem($key, $row);
        }
        return $res;
    }

    /**
     * 返回集合中所有元素,返回array()
     */
    public function smembers($key)
    {
        return self::$rd->sMembers($key);
    }

    /**
     * 求2个集合的差集,暂时只支持相同分片
     */
    public function sdiff($key, $key2)
    {
        return self::$rd->sDiff($key, $key2);
    }

    /**
     * 求2个集合的交集,暂时只支持相同分片
     */
    public function sinter($key, $key2)
    {
        return self::$rd->sInter($key, $key2);
    }

    /**
     * 求2个集合的并集,暂时只支持相同分片,并集有去重
     */
    public function sunion($key, $key2)
    {
        return self::$rd->sUnion($key, $key2);
    }

    //===========   redis sorted set string类型有序集合类型操作方法 (适合做排行榜排名)  ===========
    //sorted set 中每个元素都需要指定一个分数即权重参数score，根据分数score对元素进行升序排序，如果多个元素有相同的分数，则以字典序进行升序排序。
    //集合中元素唯一，但是分数可以重复

    /**
     * 新增：将一个元素及期分值加入到有序集合中，如果已存在，则更新其分数值，并重新插入，确保在正确的位置上
     * 如在序集合key不存在，则创建一个空的序集，当KEY存在，但不是有序类型时报错
     */
    public function zadd($keyname, $score, $value)
    {
        return self::$rd->zAdd($keyname, $score, $value);
    }

    /**
     * 查寻： $stop 为-1表示最后一个成员，-2表示倒数第二个成员。0为第一个,分值从小到大
     */
    public function zrange($keyname, $start, $stop)
    {
        return self::$rd->zRange($keyname, $start, $stop, 'withScore');
    }

    public function zrevrange($keyname, $start, $stop)
    {
        return self::$rd->zRevRange($keyname, $start, $stop, 'withScore');
    }

    /**
     * 按分数查:  zrangebyscore($keyname,'-inf','+inf'); 负无穷到正无穷之间的值，从小到大
     */
    public function zrangebyscore($keyname, $min, $max)
    {
        return self::$rd->zRangeByScore($keyname, $min, $max);
    }

    public function zrevrangebyscore($keyname, $max, $min)
    {
        return self::$rd->zRevRangeByScore($keyname, $min, $max);
    }

    /**
     * 删除：返回被成功移除的成员数量
     */
    public function zrem($keyname, $value)
    {
        if (!is_array($value)) {
            $arr = array($value);
        } else {
            $arr = $value;
        }
        $res = 0;
        foreach ($arr as $row) {
            $res += self::$rd->zRem($keyname, $row);
        }
        return $res;
    }

    /**
     * 统计元素个数
     */
    public function zcard($keyname)
    {
        return self::$rd->zCard($keyname);
    }

    /**
     * 区间统计 计算有序集合中指定分数区间的成员数量,包含该分数
     */
    public function zcount($keyname, $min, $max)
    {
        return self::$rd->zCount($keyname, $min, $max);
    }

    /**
     * 查分数：找集合中某个值的分数，不存在返回false
     */
    public function zscore($keyname, $value)
    {
        return self::$rd->zScore($keyname, $value);
    }

    /**
     * 加减分  给指定的值加减分，正为加，负为减。 $increment整型或浮点型
     */
    public function zincrby($keyname, $increment, $value)
    {
        return self::$rd->zIncrBy($keyname, $increment, $value);
    }

    /**
     * 某值在集合中的排名，从小到大 分值最小的值返回0，查不到返回false;
     */
    public function zrank($keyname, $member)
    {
        return self::$rd->zRank($keyname, $member);
    }

    /**
     * 某值在集合中的排名，从大到小 分值最大的值返回0，查不到返回false;
     */
    public function zrevrank($keyname, $member)
    {
        return self::$rd->zRevRank($keyname, $member);
    }
}
